/*****************************************************************************
 *  CommLayerLib for Windowed Systems - SerialLayer.java
 *  Copyright (C) 2012  Matteo Piscitelli
 *  E-mail: matteo@picciux.it
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 *  For further info, check http://code.google.com/p/java-comm-layer/
 *
 *  SVN: $Id: SerialLayer.java 43 2012-10-11 16:47:55Z picciux@gmail.com $
 *  
 *****************************************************************************/

package it.picciux.commlayer.win.serial;

import java.io.IOException;
import java.util.Enumeration;
import java.util.Vector;
import java.util.logging.Logger;

import it.picciux.commlayer.CommLayerException;
import it.picciux.commlayer.CommLayerPort;
import it.picciux.commlayer.DataWriter;
import gnu.io.CommPort;
import gnu.io.CommPortIdentifier;
import gnu.io.NoSuchPortException;
import gnu.io.PortInUseException;
import gnu.io.SerialPort;
import gnu.io.UnsupportedCommOperationException;

public class SerialLayer extends it.picciux.commlayer.serial.SerialLayer /*implements SerialPortEventListener*/ {
	@Override
	public Settings getSettings() {
		// TODO Auto-generated method stub
		return (Settings) super.getSettings();
	}

	private class ScanThread extends Thread {
		private boolean keepRunning = true;
		
		public synchronized boolean isRunning() {
			return keepRunning;
		}
		
		public synchronized void terminate() {
			keepRunning = false;
			interrupt();
		}
		
		@SuppressWarnings("unchecked")
		public void run() {
			log.entering("ScanThread", "run");
			notifyListeners(PORTSCAN_STARTED);
			Enumeration<CommPortIdentifier> lPortList;
			Vector<String> portVect = new Vector<String>();
			CommPortIdentifier portId;
			
			lPortList = CommPortIdentifier.getPortIdentifiers();
			while(lPortList.hasMoreElements() && isRunning()) {
				portId = (CommPortIdentifier) lPortList.nextElement();
				if (portId.getPortType() == CommPortIdentifier.PORT_SERIAL) {
					portVect.add(portId.getName());
					notifyListeners(PORTSCAN_PORT_FOUND, new CommLayerPort(portId.getName()));
				}
			}
			
			CommLayerPort[] portList = new CommLayerPort[portVect.size()];
			
			for (int i = 0; i < portList.length; i++)
				portList[i] = new CommLayerPort(portVect.get(i));

			notifyListeners(PORTSCAN_COMPLETED, portList);
			log.exiting("ScanThread", "run");
		}
		
	}
	
	private SerialPort serialPort;
	private ScanThread scanThread = null;
	
	private static Logger log = Logger.getLogger("it.picciux.commlayer.win.serial");
	
	private int flowControlToRXTX(int f) {
		switch(f) {
		case Settings.FLOWCONTROL_XONXOF:
			return SerialPort.FLOWCONTROL_XONXOFF_IN | SerialPort.FLOWCONTROL_XONXOFF_OUT;
		case Settings.FLOWCONTROL_RTSCTS:
			return SerialPort.FLOWCONTROL_RTSCTS_IN | SerialPort.FLOWCONTROL_RTSCTS_OUT;
		default:					// NONE
			return f;							
		}
	}

	protected void tryConnection() throws CommLayerException {
		CommPortIdentifier portIdentifier;
		Settings s = getSettings();
		
		try {
			portIdentifier = CommPortIdentifier.getPortIdentifier(s.getPort());
		} catch (NoSuchPortException e) {
			//notifyListeners(NOSUCHDEV, "Port '" + s.getPort() + "' not found!");
			throw new CommLayerException("Port '" + s.getPort() + "' not found!", NO_SUCH_PORT);
		}

        if ( portIdentifier.isCurrentlyOwned() ) {
			//notifyListeners(CONNECTION_BUSY, "Port '" + s.getPort() + "' is busy!");
        	throw new CommLayerException("Port '" + s.getPort() + "' is busy!", CONNECTION_BUSY);
        }
            
        CommPort commPort;

        try {
			commPort = portIdentifier.open(this.getClass().getName(),2000);
		} catch (PortInUseException e) {
        	throw new CommLayerException("Port '" + s.getPort() + "' is busy!", CONNECTION_BUSY);
		}
        
        if (! (commPort instanceof SerialPort) ) {
        	throw new CommLayerException("Port '" +s.getPort() + "' is not a COM", PORT_IS_NOT_COM);
        }
           
        serialPort = (SerialPort) commPort;
            
        try {
			serialPort.setSerialPortParams(
					s.getBaudRate(),
					s.getDataBits(),
					s.getStopBits(),
					s.getParity()
			);
			
			serialPort.setFlowControlMode(flowControlToRXTX(s.getFlowControl()));
		} catch (UnsupportedCommOperationException e) {
        	throw new CommLayerException("COM operation not supported" + e.getMessage(), CONNECTION_ERROR);
		}

        try {
			in = serialPort.getInputStream();
		} catch (IOException e) {
			throw new CommLayerException(e.getMessage(), CONNECTION_ERROR);
		}
        
        try {
			out = serialPort.getOutputStream();
		} catch (IOException e) {
			throw new CommLayerException(e.getMessage(), CONNECTION_ERROR);
		}

        if (notifyOnDataAvailable)
        	dataReader = new DataReader() {
	    		@Override
	    		public void setThreadData() {
	    			setName("Serial DataReader thread");
	    		}
        	};
        
        if (writeOnSeparateThread)
        	dataWriter = new DataWriter() {
				@Override
				protected void setThreadData() {
					setName("Serial DataWriter thread");
				}
			};
        /*
        try {
			serialPort.addEventListener(SerialLayer.this);
		} catch (TooManyListenersException e) {
			notifyListeners(CONNECTION_ERROR, e.getMessage());
			return CONNECTION_ERROR;
		}
		*/
        
        //serialPort.notifyOnDataAvailable(true);	
        
        //connected = true;
        //notifyListeners(CONNECTED);
        return;
	}
	

	@Override
	protected void doDisconnect() {
		/* Close output stream */
		try {
			out.close();
		} catch (IOException e) {
			log.fine("Exception closing output: " + e.getMessage());
		}
		
		/* close input stream */
		try {
			in.close();
		} catch (IOException e) {
			log.fine("Exception closing input: " + e.getMessage());
		}
		
		if (serialPort != null) {
			serialPort.notifyOnDataAvailable(false);
			serialPort.close();
		}		
	}

	/*@Override
	public void serialEvent(SerialPortEvent e) {
		if (e.getEventType() != SerialPortEvent.DATA_AVAILABLE) return;
    	//dataReader.eventRun();
	}*/

	@Override
	public boolean scan() {
		scanThread = new ScanThread();
		scanThread.start();
		return true;
	}

	@Override
	public void cancelScan() {
		if (scanThread != null && scanThread.isRunning()) {
			scanThread.terminate();
		}

		scanThread = null;
	}

	@Override
	protected void setConnectThread(Thread thread) {
		thread.setName("Serial connection thread");
	}

	
	
}
